﻿#region MIT
/*
Engine Of Evermore (http://code.google.com/p/engineofevermore/)
Copyright (c) 2008, 2009 Michael Woerister

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using EngineOfEvermore.Common;
using EngineOfEvermore.Scene.Physics;
using Green.Logging;


using System.Xml.Linq;
using System.Reflection;
using System.ComponentModel;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using Blue.ResourceManager;
using NUnit.Framework;
using System.IO;

namespace EngineOfEvermore.Scene
{
	[XmlObjectSerializer (typeof(SceneObjectXmlSerializer))]
    public sealed class SceneObject
    {
        #region Variables
        List<SceneObjectComponent> _components = new List<SceneObjectComponent>();
        SceneGraph _sceneGraph;
        Vector2d _position;
        float _rotation;
        Vector2d _linearVelocity;
        int _space;
        List<IPhysicsModifier> _physicsModifier = new List<IPhysicsModifier>();
        #endregion

        #region Events
        public event Action<SceneObject, Vector2d> PositionChanged;
        public event Action<SceneObject, float> RotationChanged;
        public event Action<SceneObject, Vector2d> LinearVelocityChanged;
        public event Action<SceneObject, int> SpaceChanged;
        public event Action<SceneObject, IPhysicsModifier> PhysicsModifierAdded;
        public event Action<SceneObject, IPhysicsModifier> PhysicsModifierRemoved;
        #endregion

        #region Properties
        public SceneGraph SceneGraph
        {
            get { return _sceneGraph; }
        }

        [PerInstance] public Vector2d Position
        {
            get { return _position; }
            set
            {
                if ( _position != value )
                {
                    _position = value;

                    if ( PositionChanged != null )
                    {
                        PositionChanged( this, value );
                    }
                }
            }
        }

        [PerInstance] public float Rotation
        {
            get { return _rotation; }
            set
            {
                if ( _rotation != value )
                {
                    _rotation = value;

                    if ( RotationChanged != null )
                    {
                        RotationChanged( this, value );
                    }
                }
            }
        }
		
        [PerInstance] public Vector2d LinearVelocity
        {
            get { return _linearVelocity; }
            set
            {
                if ( _linearVelocity != value )
                {
                    _linearVelocity = value;

                    if ( LinearVelocityChanged != null )
                    {
                        LinearVelocityChanged( this, value );
                    }
                }
            }
        }

        [PerInstance] public int Space
        {
            get { return _space; }
            set
            {
                Debug.Assert( value >= 0 );

                if ( _space != value )
                {
                    _space = value;

                    if ( SpaceChanged != null )
                    {
                        SpaceChanged( this, value );
                    }
                }
            }
        }
        #endregion

        #region Component Stuff
        public int ComponentCount
        {
            get { return _components.Count; }
        }

        public SceneObjectComponent GetComponent( int index )
        {
            Debug.Assert( index < _components.Count );
            return _components[index];
        }

        public T GetComponent<T> () where T : SceneObjectComponent {

            foreach ( SceneObjectComponent c in _components )
            {
                if ( c is T )
                {
                    return (T) c;
                }
            }

            return null;
        }

        public void AddComponent( SceneObjectComponent c )
        {
            if ( _sceneGraph == null || !_sceneGraph.TimeContext.IsUpdating )
            {
                _addComponent( c );
            }
            else
            {
                _sceneGraph.ScheduleComponentAddition( this, c );
            }
        }

        public void RemoveComponent( SceneObjectComponent c )
        {
            if ( _sceneGraph == null || !_sceneGraph.TimeContext.IsUpdating )
            {
                _removeComponent( c );
            }
            else
            {
                _sceneGraph.ScheduleComponentRemoval( this, c );
            }
        }

        public bool ContainsComponent( SceneObjectComponent c )
        {
            return _components.Contains( c );
        }
        #endregion

        #region Physics
        public IEnumerable<IPhysicsModifier> PhysicsModifiers
        {
            get { return _physicsModifier; }
        }

        public void AddPhysicsModifier( IPhysicsModifier mod )
        {
            _physicsModifier.Add( mod );

            if ( PhysicsModifierAdded != null )
            {
                PhysicsModifierAdded( this, mod );
            }
        }

        public void RemovePhysicsModifier( IPhysicsModifier mod )
        {
            if ( PhysicsModifierRemoved != null )
            {
                PhysicsModifierRemoved( this, mod );
            }

            _physicsModifier.Remove( mod );
        }

        public Vector2d GetWorldPosition( Vector2d localPosition )
        {
            localPosition.Rotate( this.Rotation );
            return this.Position + localPosition;
        }
        #endregion

        #region Internal SceneGraph House Keeping 
        internal void OnAddToScene( SceneGraph scene )
        {
            Debug.Assert( _sceneGraph == null );

            _sceneGraph = scene;

            foreach ( SceneObjectComponent ec in _components )
            {
                ec.OnAddToScene( scene );
            }
        }

        internal void OnRemoveFromScene( SceneGraph scene )
        {
            Debug.Assert( _sceneGraph == scene );

            foreach ( SceneObjectComponent ec in _components )
            {
                ec.OnRemoveFromScene( scene );
            }

            _sceneGraph = null;
        }

        internal void UpdateXForm( float x, float y, float rot )
        {
            _position.X = x;
            _position.Y = y;
            _rotation = rot;
        }
        #endregion

        #region Private Methods
        void _addComponent( SceneObjectComponent c )
        {
            Debug.Assert( c.Owner == null );

            _components.Add( c );

            c.OnAddToSceneObject( this );

            foreach ( SceneObjectComponent sibling in _components )
            {
                if ( c != sibling )
                {
                    sibling.OnSiblingAdded( c );
                    c.OnSiblingAdded( sibling );
                }
            }

            if ( _sceneGraph != null )
            {
                c.OnAddToScene( _sceneGraph );
            }
        }

        void _removeComponent( SceneObjectComponent c )
        {
            Debug.Assert( c.Owner == this );
            Debug.Assert( _components.Contains( c ) );

            if ( _sceneGraph != null )
            {
                c.OnRemoveFromScene( _sceneGraph );
            }

            foreach ( SceneObjectComponent sibling in _components )
            {
                if ( c != sibling )
                {
                    c.OnSiblingRemoved( sibling );
                    sibling.OnSiblingRemoved( c );
                }
            }

            c.OnRemoveFromSceneObject( this );
            _components.Remove( c );
        }
        #endregion

        #region Prototype
        /// <summary>
        /// Returns a clone of this object. The clone will not be contained in any scene graph.
        /// </summary>
        public SceneObject Clone()
        {
            SceneObject clone = new SceneObject();
            clone.Position = this.Position;
            clone.Rotation = this.Rotation;
            clone.Space = this.Space;
            clone.LinearVelocity = this.LinearVelocity;

            foreach ( var physicsModifier in this.PhysicsModifiers )
            {
                clone.AddPhysicsModifier( physicsModifier.Clone() );
            }

            foreach ( var component in _components )
            {
                clone.AddComponent( component.Clone() );
            }

            return clone;
        }
        #endregion
    }


    public class SceneObjectSerializer
    {
        public void Serialize( SceneObject sceneObject, XmlWriter writer )
        {
            //var definitionRefComponent = 
            //    sceneObject.GetComponent<ObjectLibrary.SceneObjectDefinitionRefComponent>();

            //var definition = ResourceManager.GetResource<ObjectLibrary.SceneObjectDefinition>( 
            //    definitionRefComponent.DefinitionName );

            
        }

        public void DeserializeFull( XElement xml, SceneObject sceneObject )
        {
            var positionElement = xml.Element( "Position" );
            if ( positionElement != null )
            {
                sceneObject.Position = Vector2dSerializer.Deserialize( positionElement );
            }

            var rotationElement = xml.Element( "Rotation" );
            if ( rotationElement != null )
            {
                sceneObject.Rotation = FloatSerializer.Deserialize( rotationElement );
            }

            var linearVelocityElement = xml.Element( "LinearVelocity" );
            if ( linearVelocityElement != null )
            {
                sceneObject.LinearVelocity = Vector2dSerializer.Deserialize( linearVelocityElement );
            }

            var spaceElement = xml.Element( "Space" );
            if ( spaceElement != null )
            {
                sceneObject.Space = IntSerializer.Deserialize( spaceElement );
            }

            // Deserialize Components
            var componentsElement = xml.Element( "Components" );
            if ( componentsElement != null )
            {
                foreach ( var componentElement in componentsElement.Elements() )
                {
                    SceneObjectComponentSerializer.Instance
                        .Deserialize( componentElement, sceneObject );
                }
            }
        }

        public XElement SerializeFull( SceneObject sceneObject )
        {
            var xml = 
                new XElement( "SceneObject",
                    Vector2dSerializer.Serialize( sceneObject.Position, "Position" ),
                    FloatSerializer.Serialize( sceneObject.Rotation, "Rotation" ),
                    Vector2dSerializer.Serialize( sceneObject.LinearVelocity, "LinearVelocity" ),
                    IntSerializer.Serialize( sceneObject.Space, "Space" )
                );

            var componentElements = new List<XElement>();

            for ( int i = 0; i < sceneObject.ComponentCount; ++i )
            {
                var componentXml = SceneObjectComponentSerializer.Instance
                    .Serialize( sceneObject.GetComponent( i ) );

                componentElements.Add( componentXml );
            }

            if ( componentElements.Count > 0 )
            {
                var componentsElement = new XElement( "Components" );

                foreach ( var ce in componentElements.OrderBy( e => e.Name ) )
                {
                    componentsElement.Add( ce );
                }

                xml.Add( componentsElement );
            }

            return xml;
        }
    }

    

    namespace Test {

        [TestFixture]
        public class SceneObjectSerializerFixture 
        {
            private static XNodeEqualityComparer _comp = new XNodeEqualityComparer();

            private class TestComponent1 : SceneObjectComponent
            {
                public int PropX = 0;

                public override SceneObjectComponent Clone()
                {
                    return new TestComponent1();
                }
            }

            private static void _deserializeTestComponent1( XElement xml,
                SceneObjectComponent component )
            {
                var c = (TestComponent1) component;

                c.PropX = IntSerializer.Deserialize( xml.Element( "PropX" ) );
            }

            private static XElement _serializeTestComponent1( SceneObjectComponent component )
            {
                Debug.Assert( component is TestComponent1 );

                TestComponent1 c = (TestComponent1) component;

                return new XElement( "TestComponent1",
                    IntSerializer.Serialize( c.PropX, "PropX" )
                    );
            }

            private SceneObjectSerializer _serializer = new SceneObjectSerializer();

            [SetUp]
            public void SetUp()
            {
                SceneObjectComponentSerializer.Instance.ClearRegistry();
            }

            [Test]
            public void TestDeserializeWithoutComponents()
            {
                var xml =
                    new XElement( "SceneObject",
                        new XElement( "Position", "1 2" ),
                        new XElement( "Rotation", "90" ),
                        new XElement( "LinearVelocity", "0 22" ),
                        new XElement( "Space", "23" )
                        );

                var sceneObject = new SceneObject();
                
                _serializer.DeserializeFull( xml, sceneObject );

                Assert.That( sceneObject.Position.EpsilonEquals( new Vector2d( 1, 2 ) ) );
                Assert.That( sceneObject.Rotation.EpsilonEquals( 90 ) );
                Assert.That( sceneObject.LinearVelocity.EpsilonEquals( new Vector2d( 0, 22 ) ) );
                Assert.That( sceneObject.Space, Is.EqualTo( 23 ) );
            }

            [Test]
            public void TestDeserializeWithTestComponent()
            {
                SceneObjectComponentSerializer.Instance
                    .RegisterDeserializer<TestComponent1>( "TestComponent1",
                        _deserializeTestComponent1 );

                var xml =
                    new XElement( "SceneObject",
                        new XElement( "Components",
                            new XElement( "TestComponent1",
                                new XElement( "PropX", "283" ) 
                                )
                            )
                        );

                var sceneObject = new SceneObject();
                
                _serializer.DeserializeFull( xml, sceneObject );

                Assert.That( sceneObject.ComponentCount, Is.EqualTo( 1 ) );
                Assert.That( sceneObject.GetComponent<TestComponent1>(), Is.Not.Null );
                Assert.That( sceneObject.GetComponent<TestComponent1>().PropX, Is.EqualTo( 283 ) );
            }

            [Test]
            public void TestSerializeWithoutComponents()
            {
                var sceneObject = new SceneObject();

                sceneObject.Position = new Vector2d( 10, 20 );
                sceneObject.Rotation = 91;
                sceneObject.LinearVelocity = new Vector2d( 33, 44 );
                sceneObject.Space = 8;

                var xml = _serializer.SerializeFull( sceneObject );

                var expectedXml =
                    new XElement( "SceneObject",
                        new XElement( "Position", "10 20" ),
                        new XElement( "Rotation", "91" ),
                        new XElement( "LinearVelocity", "33 44" ),
                        new XElement( "Space", "8" )
                        );

                Assert.That( _comp.Equals( xml, expectedXml ) );
            }

            [Test]
            public void TestSerializeWithTestComponent()
            {
                SceneObjectComponentSerializer.Instance.RegisterSerializer<TestComponent1>( 
                    "TestComponent1", _serializeTestComponent1 );

                var sceneObject = new SceneObject();

                sceneObject.Position = new Vector2d( 10, 20 );
                sceneObject.Rotation = 91;
                sceneObject.LinearVelocity = new Vector2d( 33, 44 );
                sceneObject.Space = 8;

                var component = new TestComponent1();

                component.PropX = 101;

                sceneObject.AddComponent( component );

                var xml = _serializer.SerializeFull( sceneObject );

                var expectedXml =
                    new XElement( "SceneObject",
                        new XElement( "Position", "10 20" ),
                        new XElement( "Rotation", "91" ),
                        new XElement( "LinearVelocity", "33 44" ),
                        new XElement( "Space", "8" ),
                        new XElement( "Components",
                            new XElement( "TestComponent1",
                                new XElement( "PropX", "101" )
                                )
                            )
                        );

                Assert.That( _comp.Equals( xml, expectedXml ) );
            }
        }
    }
    

	
	class SceneObjectXmlSerializer : IXmlObjectSerializer
	{
			
		private SceneObject _obj = null;
		
		public Object Obj {
			get {
				return ( Object ) _obj;
			}
			
			set {
				if ( value is SceneObject )
					_obj = ( SceneObject ) value;
				else
					_obj = null; // TODO: throw exception?
				
			}
		}
		
		public void getXml( XElement node, XmlSerialization serializer ) 
		{
			// check if the SceneObject has a SceneObjectDefinitionRefComponent component
			// if yes, only store the referece and set the serializer to perInstance mode
			Scene.ObjectLibrary.SceneObjectDefinitionRefComponent def = 
				_obj.GetComponent<Scene.ObjectLibrary.SceneObjectDefinitionRefComponent>();
			
			SerializationTypes serializationType = serializer.SerializationType;			
			if ( def != null ) {
				node.Add ( new XElement( "SceneObjectDefinition", def.DefinitionName ) );
				serializer.SerializationType = SerializationTypes.perInstance;
			}
			
			serializer.getXmlFromProperties( _obj, node );
			
			// store all components
			XElement comps = new XElement( "Components" );
			for( int i = 0; i < _obj.ComponentCount; i++ ) {
				serializer.getXmlFromObject( _obj.GetComponent( i ), comps );
			}
			node.Add( comps );
			
			// physicsModifier
			XElement physmods = new XElement( "PhysicsModifier" );
			foreach( IPhysicsModifier ph in _obj.PhysicsModifiers ) {
				serializer.getXmlFromObject( ph, physmods );
			}
			node.Add( physmods );
			
			// restore serializer mode
			serializer.SerializationType = serializationType;
		}
			
		
		public Object loadFromXml( XElement node, XmlSerialization serializer )
		{
			SerializationTypes serType = serializer.SerializationType;
			
			// check if the xml has a "SceneObjectDefinition" element, if yes
			// create the object from the SceneObjectDefinition
			if ( node.Element( "SceneObjectDefinition" ) != null ) {
				ObjectLibrary.SceneObjectDefinition def = 
					ResourceManager.GetResource<ObjectLibrary.SceneObjectDefinition>( node.Element( "SceneObjectDefinition" ).Value );
				
				_obj = def.CreateSceneObject();
				serializer.SerializationType = SerializationTypes.perInstance;
			} else {
				_obj = new SceneObject();
			}
			
			// properties
			serializer.loadPropertiesFromXml( _obj, node );

			// components
			foreach( XElement comp in node.Element( "Components" ).Elements() ) {
				Object c = serializer.loadObjectFromXml( comp );
				
				// check if the component type to be added was not added already
				for ( int i = 0; i < _obj.ComponentCount; i++ ) {
					if ( c.GetType() == _obj.GetComponent( i ).GetType() ) {
						throw new ArgumentException( "adding a component to a SceneObject that already contains"+
											" a component of this type is currently not supported" );
					}
				}
				
				if ( c is SceneObjectComponent ) {
					_obj.AddComponent( (SceneObjectComponent) c );
				} else {
					throw new XmlSerializationException( "object '" + comp.Name + "' is no valid SceneObjectComponent" );
				}
			}
			
			
			// physicsModifier
			foreach( XElement phy in node.Element( "PhysicsModifier").Elements() ) {
				Object p = serializer.loadObjectFromXml(phy);
				if ( p is IPhysicsModifier ) {
					_obj.AddPhysicsModifier( (IPhysicsModifier) p );
				} else {
					throw new XmlSerializationException( "object '" + phy.Name + "' is no valid PhysicsModifier" );
				}
			}
			
			serializer.SerializationType = serType;
			
			return _obj;	
		}
	}
		
    #if ENGINE_OF_EVERMORE_ENABLE_UNIT_TESTS
    namespace Test
    {
        [TestFixture]
        public class SceneObjectTestFixture
        {
            [Test]
            public void TestGetWorldPositionAtCenterNoRotation()
            {
                var sceneObject = new SceneObject();

                sceneObject.Position = new Vector2d( 20, 25 );

                var worldPosition = sceneObject.GetWorldPosition( Vector2d.Zero );

                Assert.That( worldPosition.EpsilonEquals( sceneObject.Position ) );
            }

            [Test]
            public void TestGetWorldPositionWithOffsetNoRotation()
            {
                var sceneObject = new SceneObject();

                sceneObject.Position = new Vector2d( 20, 25 );

                var localPosition = new Vector2d( 10, 3 );

                var worldPosition = sceneObject.GetWorldPosition( localPosition );

                Assert.That( worldPosition.EpsilonEquals( sceneObject.Position + localPosition ) );
            }

            [Test]
            public void TestGetWorldPositionWithOffsetAndRotation()
            {
                var sceneObject = new SceneObject();

                sceneObject.Position = new Vector2d( 20, 25 );
                sceneObject.Rotation = 20;

                var localPosition = new Vector2d( 10, 3 );

                var expectedPosition = localPosition;
                expectedPosition.Rotate( sceneObject.Rotation );
                expectedPosition += sceneObject.Position;

                var worldPosition = sceneObject.GetWorldPosition( localPosition );

                Assert.That( worldPosition.EpsilonEquals( expectedPosition ) );
            }

            [Test]
            public void TestCloneProperties()
            {
                var sceneObject = new SceneObject();
                sceneObject.Position = new Vector2d( 20, 320 );
                sceneObject.Rotation = 53;
                sceneObject.Space = 2;
                sceneObject.LinearVelocity = new Vector2d( 42, 23 );

                var clone = sceneObject.Clone();

                Assert.That( sceneObject.Position, Is.EqualTo( clone.Position ) );
                Assert.That( sceneObject.Rotation, Is.EqualTo( clone.Rotation ) );
                Assert.That( sceneObject.LinearVelocity, Is.EqualTo( clone.LinearVelocity ) );
                Assert.That( sceneObject.Space, Is.EqualTo( clone.Space ) );
            }

            [Test]
            public void TestCloneIsNotInAnyScene()
            {
                var sceneObjectOutsideOfAnyScene = new SceneObject();
                
                var scene = new SceneGraph();
                scene.OnInit( new System.ComponentModel.Design.ServiceContainer() );

                var sceneObjectInScene = new SceneObject();
                scene.AddSceneObject( sceneObjectInScene );

                var clone1 = sceneObjectOutsideOfAnyScene.Clone();
                var clone2 = sceneObjectInScene.Clone();

                Assert.That( clone1.SceneGraph, Is.Null );
                Assert.That( clone2.SceneGraph, Is.Null );
            }

            class TestPhysicsModifier : IPhysicsModifier
            {
                int _data;

                public TestPhysicsModifier( int data )
                {
                    _data = data;
                }

                public void OnAddToSceneObject( IPhysicsHandle ph ) {}

                public void OnRemoveFromSceneObject( IPhysicsHandle ph ) {}

                public IPhysicsModifier Clone()
                {
                    var clone = new TestPhysicsModifier( _data );
                    return clone;
                }

                public override bool Equals( object obj )
                {
                    if ( obj is TestPhysicsModifier )
                    {
                        return ( (TestPhysicsModifier) obj )._data == _data;
                    }

                    return false;
                }

                public override int GetHashCode()
                {
                    return _data.GetHashCode();
                }
            }

            [Test]
            public void TestCloningPhysicsModifiers()
            {
                var modifier1 = new TestPhysicsModifier( 100 );
                var modifier2 = new TestPhysicsModifier( 2000 );

                var sceneObject = new SceneObject();
                sceneObject.AddPhysicsModifier( modifier1 );
                sceneObject.AddPhysicsModifier( modifier2 );

                var clone = sceneObject.Clone();

                // Assert that the modifiers are equal
                Assert.That( sceneObject.PhysicsModifiers
                    .IsEquivalentTo( clone.PhysicsModifiers ) );

                // Assert that they are they are separate instances
                foreach ( var mod in sceneObject.PhysicsModifiers )
                {
                    var clonedMod = clone.PhysicsModifiers.Single( m => m.Equals( mod ) );

                    Assert.That( clonedMod, Is.Not.SameAs( mod ) );
                }
            }

            class TestComponent : SceneObjectComponent 
            {
                private int _data;
                public TestComponent( int data ) { _data = data; }
                public override bool Equals( object obj )
                {
                    var component = obj as TestComponent;
                    return component != null ? component._data == _data : false;
                }

                public override SceneObjectComponent Clone()
                {
                    return new TestComponent( _data );
                }
            }

            [Test]
            public void TestCloningComponents()
            {
                var component1 = new TestComponent( 1 );
                var component2 = new TestComponent( 2 );

                var sceneObject = new SceneObject();
                sceneObject.AddComponent( component1 );
                sceneObject.AddComponent( component2 );

                var clone = sceneObject.Clone();

                Assert.That( sceneObject.ComponentCount, Is.EqualTo( clone.ComponentCount ) );

                for ( int i = 0; i < sceneObject.ComponentCount; ++i )
                {
                    var originalComponent = sceneObject.GetComponent( i );

                    SceneObjectComponent clonedComponent = null;

                    for ( int j = 0; j < clone.ComponentCount; ++j )
                    {
                        if ( clone.GetComponent( j ).Equals( originalComponent ) )
                        {
                            clonedComponent = clone.GetComponent( j );
                            break;
                        }
                    }

                    Assert.That( clonedComponent, Is.Not.Null );
                    Assert.That( clonedComponent, Is.Not.SameAs( originalComponent ) );
                }
            }
        }
    }
    #endif
}

